/*

	DS Mario
	Clone of the classic platformer on NES for the DS

	Source code / implementation:
	Copyright (c) 2008 Samuel Saint-Pettersen <samji@bigfoot.com>

	Released under the MIT License
*/

#define GAME_VERSION 0.1 // Game version
#define GPSCREEN 0 // Use the bottom screen for gameplay (0)
#define STATSCREEN 1 // Use the top screen for statboard (1)
#define LIVES 3 // Number of lives to give Mario (3)

#include "PA9.h" // Include for PA_Lib

// Includes for graphics
#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"

// Include for audio
#include "audio.h"

/*
	Statboard class
*/
class Statboard {

	u16 score;

public:

	// Operations for points - Add or subtract points to/from score
	enum PointOps { add, sub };

	/*
		Display the statboard
	*/
	Statboard() {

		PA_InitText(STATSCREEN, 2);
		//...
	}

	/*
		Update the score
	*/
	void updateScore(PointOps op, u16 points) {

		switch(op) {

			case add:
				score = score + points;
				break;

			case sub:
				// Only subtract points when score > 0
				if(score > 0) score = score - points;
				break;
		}
	}

	// ...
};

/*
	Player class for Mario character 
*/
class Player {

	u8 avLives;

public:

	// Operations for lives - Add or subtract lives
	enum LiveOps { add, sub };

	// Available actions for Mario
	enum Actions { stand, walk, jump, die };

	// Available directions for Mario to move
	enum Directions { right, left, up, down };

	/* 
		Spawn the player character (Mario) at beginning of level (12, 120)
		TODO: Make him fall from sky on spawn
	*/
	Player() {

		// On first spawn have maximum lives
		avLives = LIVES;

		// Load palette for sprite
		PA_LoadSpritePal(GPSCREEN, 0, (void*)mario_Pal); 

		 // Load sprite itself
		PA_CreateSprite(GPSCREEN, 0, (void*)mario_Sprite, 
		OBJ_SIZE_64X32, 1, 0, 12, 120);

		setAction(stand); // Set Mario to stand
	}

	/*
		Get available lives
	*/
	u8 getLives() {

		return avLives;
	}

	/*
		Increment or decrement lives
	*/
	void setLives(LiveOps op) {

		switch(op) {

			case add:
				avLives = avLives + 1;
				break;

			case sub:
				avLives = avLives - 1;
				break;
		}
	}

	/*
		Set XY position of Mario
	*/
	void setXYPos(s16 x, s16 y) {

		PA_SetSpriteXY(GPSCREEN, 0, x, y);
	}

	/*
		Get X position of Mario
	*/
	s16 getXPos() {

		return PA_GetSpriteX(GPSCREEN, 0);
	}

	/*
		Get Y position of Mario
	*/
	s16 getYPos() {

		return PA_GetSpriteY(GPSCREEN, 0);
	}

	/*
		Flip / unflip Mario horizontally
	*/
	void flip(bool faceLeft) {

		if(faceLeft) PA_SetSpriteHflip(GPSCREEN, 0, 1);
		else PA_SetSpriteHflip(GPSCREEN, 0, 0);
	}


	/* 
		Set the action of Mario
	*/
	void setAction(Actions action) {

		switch(action) {

			case stand:
				PA_SetSpriteAnim(GPSCREEN, 0, 0); // Show stand frame
				break;

			case walk: 
				// Show walk frames
				PA_SetSpriteAnim(GPSCREEN, 0, 1); 
				break;

			case jump:
				PA_PlaySimpleSound(1, mario_jump); // Play jump sound
				PA_SetSpriteAnim(GPSCREEN, 0, 2); // Show jump frame
				break;

			case die:
				PA_PlaySimpleSound(1, mario_death); // Play death sound
				PA_SetSpriteAnim(GPSCREEN, 0, 3); // Show death frame
				break;
		}
	}

	/*
		Movie Mario in one of four directions:
		Right, left, up and down
	*/    
	void move(Directions direction) {

		u8 walkOffset = 1;  // Offset for walking
		u8 jumpOffset = 2;  // Offset for jumping

		switch(direction) {

			case right:
				flip(false); // Flip Mario to face right if necessary
				setAction(walk); // Set walk frame
				setXYPos(getXPos() + walkOffset, getYPos()); // Move X position right by offset
				break;

			case left:
				flip(true); // Flip Mario to face left if necessary
				setAction(walk); // Set walk frame
				setXYPos(getXPos() - walkOffset, getYPos()); // Move X position left by offset
				break;

			case up:
				setAction(jump); // Set jump frame
				setXYPos(getXPos(), getYPos() - jumpOffset); // Move Y position up by offset
				/* TIMER HERE */
				// setXYPos(getXPos(), getYPos() + jumpOffset); // Move Y position down by offset
				break;

			case down:
				setAction(stand); // Set stand frame
				setXYPos(getXPos(), getYPos() + jumpOffset); // Move Y position up by offset
				break;
		}
	}
};

/*
	Enemy class for alls enemies
*/
class Enemy {

	const unsigned short *palette; // Enemy sprite palette
	const unsigned char *sprite; // Enemy sprite itself
	u8 spriteNo; // Enemy sprite instance number

public:

	// Available enemy types to spawn
	enum EnemyTypes { goomba, green_koopa, red_koopa };

	/*
		Spawn enemy of type at XY positions
	*/
	void spawnEnemy(EnemyTypes type, s16 x, s16 y) {

		switch(type) {

			case goomba:
				palette = goomba_Pal;
				sprite = goomba_Sprite;
				break;

			case green_koopa:
				palette = green_koopa_Pal;
				sprite = green_koopa_Sprite;
				break;

			case red_koopa:
				palette = red_koopa_Pal;
				sprite = red_koopa_Sprite;
				break;
		}

		spriteNo++; // Increment enemy sprite number

		// Load enemy sprite palette 
		PA_LoadSpritePal(GPSCREEN, spriteNo, (void*)palette);

		// Create enemy sprite
		PA_CreateSprite(GPSCREEN, spriteNo, (void*)sprite,
		OBJ_SIZE_64X64, 1, spriteNo, x, y);

		// Begin enemy sprite animation
		PA_StartSpriteAnimEx(GPSCREEN, spriteNo, 0, 1, 2, ANIM_UPDOWN, -1);
	}

	/*
		Get X position of enemy
	*/
	s16 getXPos() {

		return PA_GetSpriteX(GPSCREEN, spriteNo);
	}

	/*
		Get Y position of enemy
	*/
	s16 getYPos() {

		return PA_GetSpriteY(GPSCREEN, spriteNo);
	}

	/* 
		Flip enemy horizontally
	*/
	void flip(bool faceLeft) {

		if(faceLeft) PA_SetSpriteHflip(GPSCREEN, spriteNo, 1);
		else PA_SetSpriteHflip(GPSCREEN, spriteNo, 0);
	}
};

/*
	Goomba class
*/
class Goomba : public Enemy {

public:

	/*
		Spawn a Goomba at XY positions
	*/
	void spawn(s16 x, s16 y) {

		spawnEnemy(goomba, x, y);
	}

	/* 
		Move Goomba back and forth within its bounds
	*/
	void move() {

	}
};

/*
	Green Koopa class
*/
class GreenKoopa: public Enemy {

public:

	/*
		Spawn a Green Koopa at XY positions
	*/
	void spawn(s16 x, s16 y) {

		spawnEnemy(green_koopa, x, y);
	}

	//...
};

/*
	Red Koopa class
*/
class RedKoopa: public Enemy {

public:

	/*
		Spawn a Red Koopa at XY positions
	*/
	void spawn(s16 x, s16 y) {

		spawnEnemy(red_koopa, x, y);
	}
};

void playGame();
void movePlayer(Player &Mario);
void spawnEnemy();
bool sideCollision(Player &Mario);
void gameOver();

/*
	Main method
*/
int main() {

	PA_Init();    // Initialize PA_Lib
	PA_InitVBL(); // Initialize a standard VBL
	PA_InitSound(); // Initialize sound system

	// Set default sound settings (vol., sample rate - Hz, format)
	PA_SetDefaultSound(200, 11025, 1); 


	/* TODO: Implement game menu here */

	playGame();


	// Infinite loop to keep the program running
	while(1) PA_WaitForVBL();
	
	return 0;
}

/*
	Play the game 
*/
void playGame() {

	Player Mario; // Spawn player character ("Mario")

	spawnEnemy();

	while(1) {

		movePlayer(Mario); // Wait for player movement

		PA_WaitForVBL();
	}
}

/*
	Move the player
*/
void movePlayer(Player &Mario) {

	// Allow movement while there is no side collision with an enemy
	if(sideCollision(Mario) == false) {

		// When right thumbpad is pressed, move right
		if(Pad.Held.Right) Mario.move(Mario.right);

		// When left thumbpad is pressed, move left
		else if(Pad.Held.Left) Mario.move(Mario.left);

		// When A is pressed, move up (jump)
		else if(Pad.Held.A) Mario.move(Mario.up);

		// When no key is pressed, stand still
		else if(Pad.Released.Anykey) Mario.setAction(Mario.stand);
	}

	// In the event of a sideways collision, Mario loses a life
	else Mario.setLives(Mario.sub); 
}

/*
	Spawn an enemy
*/
void spawnEnemy() {

	Goomba A;
	A.spawn(110, 90);
}

/* 
	Check for side player-enemy collision,
	which is fatal to the player
*/
bool sideCollision(Player &Mario) {

	// TODO: Check for collison with each enemy in an array,
	// where each will be added in spawnEnemy()

	//if((Mario.getXPos() - 1) == BadGuy.getXPos()) {

	return false;
	//}
	//else return false;
}

/*
	Game over
*/
void gameOver() {

}
